<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Go OTel 应用逻辑解释 - HTTP 处理器</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
<h1>HTTP 处理器 (Handlers)</h1>

<nav>
    <ul>
        <li><a href="index.html">概述</a></li>
        <li><a href="setup.html">初始化设置</a></li>
        <li><a href="handlers.html" class="active">HTTP 处理器</a></li>
        <li><a href="services.html">业务逻辑服务</a></li>
        <li><a href="main_flow.html">主程序流程</a></li>
        <li><a href="glossary.html">名词解释</a></li>
    </ul>
</nav>

<section>
    <h2>引言</h2>
    <p>
        HTTP 处理器是 Web 应用程序中负责接收、解析 HTTP 请求，调用相应的业务逻辑，然后构造并发送 HTTP 响应的部分。在这个应用中，我们使用 <a href="glossary.html#gin">Gin</a> 框架来定义路由和编写处理器函数。
    </p>
    <p>
        一个关键点是 <a href="glossary.html#otelgin"><code>otelgin</code></a> <a href="glossary.html#middleware">中间件</a> 的作用。它会在每个请求进入时自动创建一个 <a href="glossary.html#span">Span</a> (类型为 <code>SERVER</code>)，并尝试从请求头中提取追踪上下文 (<a href="glossary.html#context-propagation">Context Propagation</a>)。然后，它将包含这个 Span 的 <a href="glossary.html#context"><code>context.Context</code></a> 放入 Gin 的上下文中 (<code>c.Request.Context()</code>)，供后续的处理器和业务逻辑使用。
    </p>
</section>

<section id="get-user-handler">
    <h2>1. 获取用户处理器 (<code>getUserHandler</code>)</h2>
    <p>
        这个函数处理 <code>GET /users/{id}</code> 请求，用于获取指定 ID 的用户信息。
    </p>
    <pre><code class="language-go">// getUserHandler 处理 GET /users/:id 请求
func getUserHandler(c *gin.Context) {
    <span class="code-comment">// 1. 从 Gin Context 获取继承了 OTel Span 的父 Context</span>
    ctx := c.Request.Context()

    <span class="code-comment">// 2. 获取路径参数 {id}</span>
    userID := c.Param("id")
    if userID == "" {
        c.JSON(http.StatusBadRequest, gin.H{"error": "用户 ID 不能为空"})
        return
    }

    <span class="code-comment">// 3. 手动创建 Handler 内部的子 Span (可选但推荐)</span>
    var handlerSpan trace.Span
    ctx, handlerSpan = tracer.Start(ctx, "Handler: GetUserByID", <span class="code-comment">// Span 名称</span>
        trace.WithAttributes( <span class="code-comment">// 添加 Span 属性</span>
            attribute.String("app.user.id", userID),
            semconv.HTTPRequestMethodKey.String(c.Request.Method),
            semconv.URLPathKey.String(c.FullPath()),
        ),
        trace.WithSpanKind(trace.SpanKindInternal), <span class="code-comment">// 标记为内部处理步骤</span>
    )
    defer handlerSpan.End() <span class="code-comment">// 确保 Span 在函数结束时关闭</span>

    <span class="code-comment">// 4. 添加事件到 Span</span>
    handlerSpan.AddEvent("开始处理 GetUser 请求")

    <span class="code-comment">// 5. 调用业务逻辑层函数，传递包含 OTel Span 的 Context</span>
    user, err := findUserByID(ctx, userID)

    <span class="code-comment">// 6. 处理业务逻辑返回的结果</span>
    if err != nil {
        <span class="code-comment">// 记录错误到 Span</span>
        handlerSpan.RecordError(err, trace.WithAttributes(/*...*/))
        if errors.Is(err, gorm.ErrRecordNotFound) {
            <span class="code-comment">// 设置 Span 状态为 Error</span>
            handlerSpan.SetStatus(codes.Error, "User not found")
            c.JSON(http.StatusNotFound, gin.H{"error": "用户未找到"})
        } else {
            handlerSpan.SetStatus(codes.Error, "Internal error finding user")
            c.JSON(http.StatusInternalServerError, gin.H{"error": /*...*/})
        }
        return <span class="code-comment">// 返回错误响应</span>
    }

    <span class="code-comment">// 7. 如果成功找到用户</span>
    handlerSpan.SetStatus(codes.Ok, "User found") <span class="code-comment">// 设置 Span 状态为 OK</span>
    handlerSpan.AddEvent("成功获取用户数据", trace.WithAttributes(/*...*/))
    handlerSpan.SetAttributes(attribute.String("app.user.retrieved_id", user.ID)) <span class="code-comment">// 添加成功属性</span>

    <span class="code-comment">// 返回 200 OK 和用户数据</span>
    c.JSON(http.StatusOK, user)
}
</code></pre>
    <h3>逻辑流程图:</h3>
    <div class="diagram">
        <svg width="450" height="780" xmlns="http://www.w3.org/2000/svg">
            <defs><marker id="arrowhead" markerWidth="10" markerHeight="7" refX="0" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#6c757d" /></marker></defs>

            <rect x="125" y="20" width="200" height="50" class="svg-rect"/>
            <text x="225" y="45" class="svg-text">收到 GET /users/{id} 请求</text>

            <rect x="125" y="100" width="200" height="40" class="svg-rect"/>
            <text x="225" y="120" class="svg-text">otelgin 中间件处理</text>
            <text x="225" y="135" class="svg-text" style="font-size:10px">(创建 Server Span, 放入 Context)</text>


            <rect x="125" y="170" width="200" height="40" class="svg-rect"/>
            <text x="225" y="190" class="svg-text">进入 getUserHandler</text>

            <rect x="125" y="240" width="200" height="40" class="svg-rect"/>
            <text x="225" y="260" class="svg-text">获取 Context & user ID</text>

            <rect x="125" y="310" width="200" height="50" class="svg-rect"/>
            <text x="225" y="330" class="svg-text">创建 Handler Span</text>
            <text x="225" y="345" class="svg-text" style="font-size:10px">(作为 Server Span 的子 Span)</text>

            <rect x="125" y="390" width="200" height="40" class="svg-rect"/>
            <text x="225" y="410" class="svg-text">调用 findUserByID(ctx, id)</text>

            <polygon points="225,460 305,500 225,540 145,500" class="svg-diamond"/>
            <text x="225" y="500" class="svg-text">业务逻辑成功?</text>

            <rect x="355" y="480" width="150" height="40" class="svg-rect"/>
            <text x="430" y="500" class="svg-text">记录错误 (RecordError)</text>
            <rect x="355" y="540" width="150" height="40" class="svg-rect"/>
            <text x="430" y="560" class="svg-text">设置 Span 状态 (Error)</text>
            <rect x="355" y="600" width="150" height="40" class="svg-rect"/>
            <text x="430" y="620" class="svg-text">发送 404/500 响应</text>


            <rect x="5" y="570" width="150" height="40" class="svg-rect"/>
            <text x="80" y="590" class="svg-text">设置 Span 状态 (OK)</text>
            <rect x="5" y="630" width="150" height="40" class="svg-rect"/>
            <text x="80" y="650" class="svg-text">添加成功事件/属性</text>
            <rect x="5" y="690" width="150" height="40" class="svg-rect"/>
            <text x="80" y="710" class="svg-text">发送 200 响应</text>

            <rect x="125" y="740" width="200" height="40" class="svg-rect"/>
            <text x="225" y="760" class="svg-text">结束 Handler Span (defer)</text>


            <!-- Arrows -->
            <line x1="225" y1="70" x2="225" y2="100" class="svg-arrow"/>
            <line x1="225" y1="140" x2="225" y2="170" class="svg-arrow"/>
            <line x1="225" y1="210" x2="225" y2="240" class="svg-arrow"/>
            <line x1="225" y1="280" x2="225" y2="310" class="svg-arrow"/>
            <line x1="225" y1="360" x2="225" y2="390" class="svg-arrow"/>
            <line x1="225" y1="430" x2="225" y2="460" class="svg-arrow"/>

            <line x1="305" y1="500" x2="355" y2="500" class="svg-arrow"/> <text x="330" y="490" class="svg-label">否</text>
            <line x1="430" y1="520" x2="430" y2="540" class="svg-arrow"/>
            <line x1="430" y1="580" x2="430" y2="600" class="svg-arrow"/>
            <line x1="430" y1="640" x2="225" y2="740" class="svg-arrow"/> <!-- Error path to end -->


            <line x1="145" y1="500" x2="80" y2="570" class="svg-arrow"/> <text x="120" y="525" class="svg-label">是</text>
            <line x1="80" y1="610" x2="80" y2="630" class="svg-arrow"/>
            <line x1="80" y1="670" x2="80" y2="690" class="svg-arrow"/>
            <line x1="80" y1="730" x2="125" y2="740" class="svg-arrow"/> <!-- Success path to end -->

        </svg>
    </div>
    <h3>解释:</h3>
    <ol>
        <li><strong>获取 Context:</strong> 从 <code>c.Request.Context()</code> 获取上下文，这个上下文包含了由 <code>otelgin</code> 创建的父 Span。</li>
        <li><strong>解析参数:</strong> 从 URL 路径中提取 <code>id</code>。</li>
        <li><strong>创建 Handler Span:</strong> 使用全局 <code>tracer</code> 和传入的 <code>ctx</code>，手动创建一个名为 "Handler: GetUserByID" 的子 Span。这有助于将 Handler 内部的逻辑与 Gin 中间件或业务逻辑区分开。添加相关属性（用户ID、HTTP方法等）并设置 Span Kind 为 <code>INTERNAL</code>。<code>defer handlerSpan.End()</code> 确保不论函数如何退出，这个 Span 都会被正确结束。</li>
        <li><strong>添加事件:</strong> 使用 <code>AddEvent</code> 记录处理过程中的一个时间点。</li>
        <li><strong>调用业务逻辑:</strong> 调用 <code>findUserByID</code> 函数，<span class="highlight">关键是必须将包含当前 Span 的 <code>ctx</code> 传递下去</span>，这样 <code>findUserByID</code> 内部创建的 Span 才能成为这个 Handler Span 的子 Span，形成正确的追踪链。</li>
        <li><strong>错误处理:</strong> 如果业务逻辑返回错误 (<code>err != nil</code>)，使用 <code>handlerSpan.RecordError(err)</code> 将错误信息记录到 Span 中，并使用 <code>handlerSpan.SetStatus(codes.Error, "...")</code> 将 Span 的状态标记为错误。根据具体错误类型（如“未找到”或“内部错误”）返回不同的 HTTP 状态码和响应体。</li>
        <li><strong>成功处理:</strong> 如果没有错误，使用 <code>handlerSpan.SetStatus(codes.Ok, "...")</code> 标记 Span 为成功状态，可以添加额外的成功事件或属性，并返回 200 OK 响应和用户数据。</li>
    </ol>
</section>

<section id="create-user-handler">
    <h2>2. 创建用户处理器 (<code>createUserHandler</code>)</h2>
    <p>
        这个函数处理 <code>POST /users</code> 请求，用于创建新用户。
    </p>
    <pre><code class="language-go">// createUserHandler 处理 POST /users 请求
func createUserHandler(c *gin.Context) {
    ctx := c.Request.Context() // 1. 获取 OTel Context
    var newUser User

    if err := c.ShouldBindJSON(&newUser); err != nil { // 2. 绑定请求体 JSON
        c.JSON(http.StatusBadRequest, gin.H{"error": /*...*/})
        return
    }

    // 3. 创建 Handler 子 Span
    var handlerSpan trace.Span
    ctx, handlerSpan = tracer.Start(ctx, "Handler: CreateUser",
        trace.WithAttributes(
            attribute.Bool("app.user.email_provided", newUser.Email != ""), // 使用 Bool
            /*...*/
        ),
        trace.WithSpanKind(trace.SpanKindInternal),
    )
    defer handlerSpan.End()

    handlerSpan.AddEvent("开始处理 CreateUser 请求") // 4. 添加事件

    // 5. 生成 ID (如果需要)
    if newUser.ID == "" {
        newUser.ID = uuid.NewString()
        handlerSpan.SetAttributes(/*... id_generated=true ...*/)
    } else {
        handlerSpan.SetAttributes(/*... id_generated=false ...*/)
    }

    // 6. 基本输入校验
    if newUser.Name == "" || newUser.Email == "" {
        err := errors.New("用户名和邮箱不能为空")
        handlerSpan.RecordError(err)
        handlerSpan.SetStatus(codes.Error, "Invalid input data")
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // 7. 调用业务逻辑层 (传递 OTel Context)
    createdUser, err := saveUser(ctx, &newUser)

    // 8. 处理结果
    if err != nil {
        handlerSpan.RecordError(err, trace.WithAttributes(/*...*/))
        handlerSpan.SetStatus(codes.Error, "Failed to create user")
        c.JSON(http.StatusInternalServerError, gin.H{"error": /*...*/})
        return
    }

    // 9. 成功处理
    handlerSpan.SetStatus(codes.Ok, "User created")
    handlerSpan.SetAttributes(attribute.String("app.user.created_id", createdUser.ID))
    handlerSpan.AddEvent("用户创建成功")
    c.JSON(http.StatusCreated, createdUser) // 返回 201 Created
}
</code></pre>
    <h3>逻辑流程图 (与 GetUser 类似，细节不同):</h3>
    <div class="diagram">
        <svg width="450" height="820" xmlns="http://www.w3.org/2000/svg">
            <defs><marker id="arrowhead" markerWidth="10" markerHeight="7" refX="0" refY="3.5" orient="auto"><polygon points="0 0, 10 3.5, 0 7" fill="#6c757d" /></marker></defs>

            <rect x="125" y="20" width="200" height="50" class="svg-rect"/>
            <text x="225" y="45" class="svg-text">收到 POST /users 请求</text>

            <rect x="125" y="100" width="200" height="40" class="svg-rect"/>
            <text x="225" y="120" class="svg-text">otelgin 中间件处理</text>

            <rect x="125" y="170" width="200" height="40" class="svg-rect"/>
            <text x="225" y="190" class="svg-text">进入 createUserHandler</text>

            <rect x="125" y="240" width="200" height="40" class="svg-rect"/>
            <text x="225" y="260" class="svg-text">获取 Context & 绑定 JSON</text>

            <rect x="125" y="310" width="200" height="50" class="svg-rect"/>
            <text x="225" y="330" class="svg-text">创建 Handler Span</text>
            <text x="225" y="345" class="svg-text" style="font-size:10px">(作为 Server Span 的子 Span)</text>

            <rect x="125" y="390" width="200" height="40" class="svg-rect"/>
            <text x="225" y="410" class="svg-text">生成 UUID (如果需要)</text>

            <rect x="125" y="460" width="200" height="40" class="svg-rect"/>
            <text x="225" y="480" class="svg-text">输入校验</text>

            <polygon points="225,520 285,550 225,580 165,550" class="svg-diamond"/>
            <text x="225" y="550" class="svg-text">校验通过?</text>

            <rect x="125" y="610" width="200" height="40" class="svg-rect"/>
            <text x="225" y="630" class="svg-text">调用 saveUser(ctx, &newUser)</text>

            <polygon points="225,670 305,710 225,750 145,710" class="svg-diamond"/>
            <text x="225" y="710" class="svg-text">业务逻辑成功?</text>

            <!-- Error Paths -->
            <rect x="335" y="530" width="150" height="40" class="svg-rect"/>
            <text x="410" y="550" class="svg-text">记录错误 & 设状态</text>
            <rect x="335" y="590" width="150" height="40" class="svg-rect"/>
            <text x="410" y="610" class="svg-text">发送 400 响应</text>

            <rect x="355" y="690" width="150" height="40" class="svg-rect"/>
            <text x="430" y="710" class="svg-text">记录错误 & 设状态</text>
            <rect x="355" y="750" width="150" height="40" class="svg-rect"/>
            <text x="430" y="770" class="svg-text">发送 500 响应</text>

            <!-- Success Path -->
            <rect x="5" y="750" width="150" height="40" class="svg-rect"/>
            <text x="80" y="770" class="svg-text">设置 Span 状态 (OK)</text>
            <rect x="5" y="810" width="150" height="40" class="svg-rect"/>
            <text x="80" y="830" class="svg-text">添加成功事件/属性</text>
            <rect x="5" y="870" width="150" height="40" class="svg-rect"/>
            <text x="80" y="890" class="svg-text">发送 201 响应</text>

            <rect x="125" y="930" width="200" height="40" class="svg-rect"/>
            <text x="225" y="950" class="svg-text">结束 Handler Span (defer)</text>

            <!-- Arrows -->
            <line x1="225" y1="70" x2="225" y2="100" class="svg-arrow"/>
            <line x1="225" y1="140" x2="225" y2="170" class="svg-arrow"/>
            <line x1="225" y1="210" x2="225" y2="240" class="svg-arrow"/>
            <line x1="225" y1="280" x2="225" y2="310" class="svg-arrow"/>
            <line x1="225" y1="360" x2="225" y2="390" class="svg-arrow"/>
            <line x1="225" y1="430" x2="225" y2="460" class="svg-arrow"/>
            <line x1="225" y1="500" x2="225" y2="520" class="svg-arrow"/>

            <line x1="285" y1="550" x2="335" y2="550" class="svg-arrow"/> <text x="310" y="540" class="svg-label">否</text>
            <line x1="410" y1="570" x2="410" y2="590" class="svg-arrow"/>
            <line x1="410" y1="630" x2="225" y2="930" class="svg-arrow"/> <!-- Validation Error path to end -->

            <line x1="225" y1="580" x2="225" y2="610" class="svg-arrow"/> <text x="200" y="595" class="svg-label">是</text>
            <line x1="225" y1="650" x2="225" y2="670" class="svg-arrow"/>

            <line x1="305" y1="710" x2="355" y2="710" class="svg-arrow"/> <text x="330" y="700" class="svg-label">否</text>
            <line x1="430" y1="730" x2="430" y2="750" class="svg-arrow"/>
            <line x1="430" y1="790" x2="225" y2="930" class="svg-arrow"/> <!-- Service Error path to end -->

            <line x1="145" y1="710" x2="80" y2="750" class="svg-arrow"/> <text x="120" y="730" class="svg-label">是</text>
            <line x1="80" y1="790" x2="80" y2="810" class="svg-arrow"/>
            <line x1="80" y1="850" x2="80" y2="870" class="svg-arrow"/>
            <line x1="80" y1="910" x2="125" y2="930" class="svg-arrow"/> <!-- Success path to end -->
        </svg>
    </div>
    <h3>解释:</h3>
    <p>
        <code>createUserHandler</code> 的结构与 <code>getUserHandler</code> 非常相似，主要区别在于：
    </p>
    <ul>
        <li><strong>解析请求体:</strong> 使用 <code>c.ShouldBindJSON(&newUser)</code> 从 POST 请求的 Body 中解析 JSON 数据到 <code>newUser</code> 结构体。</li>
        <li><strong>生成 ID:</strong> 如果请求中没有提供用户 ID (<code>newUser.ID == ""</code>)，则使用 <code>uuid.NewString()</code> 生成一个新的唯一 ID，并在 Span 中记录 ID 是自动生成的。</li>
        <li><strong>输入校验:</strong> 增加了一个简单的校验，确保用户名和邮箱不为空。</li>
        <li><strong>调用 <code>saveUser</code>:</strong> 调用业务逻辑层函数来保存用户，同样传递 OTel Context。</li>
        <li><strong>响应状态码:</strong> 成功时返回 HTTP 状态码 <code>201 Created</code>，表示资源已成功创建。</li>
    </ul>
    <p>
        同样地，错误处理和 OTel Span 的管理（创建、记录错误/事件、设置状态、结束）是贯穿始终的。
    </p>
</section>

</body>
</html>